home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1992 June: ROMin Holiday / ADC Developer CD (1992-06) (''ROMin Holiday'')_iso / Developer Connection - 06-1992.iso / Developer Essentials / DTS Sample Code / System 7.0 Samples / CShell⁄THINK C / TextEditControl.c < prev    next >
Encoding:
Text File  |  1991-02-20  |  50.4 KB  |  1,972 lines  |  [TEXT/MPS ]

  1. /*
  2. ** Apple Macintosh Developer Technical Support
  3. **
  4. ** Program:         texteditcontrol.c
  5. ** Written by:      Eric Soldan
  6. ** Based on:        TESample, by Bryan Stearns
  7. ** Suggestions by:  Forrest Tanaka, Dave Radcliffe
  8. **
  9. ** Copyright © 1990-1991 Apple Computer, Inc.
  10. ** All rights reserved.
  11. */
  12.  
  13. /* This is a control implementation of TextEdit.  The advantages to this are:
  14. **
  15. ** 1) Makes using TextEdit in a non-dialog window easy.
  16. ** 2) The TextEdit record is automatically associated with the window, since
  17. **    it is in the window's control list.
  18. ** 3) The TextEdit control can have scrollbars associated with it, and these
  19. **    are also kept in the window's control list.
  20. ** 4) Updating of the TextEdit record is much simpler, since all that is
  21. **    necessary is to draw the control (or all the window's controls with
  22. **    a DrawControls call).
  23. ** 5) There are simple calls to handle TextEdit events.
  24. ** 6) Undo is already supported.
  25. ** 7) A document length can be specified.  This length will not be exceeded
  26. **    when editing the TextEdit record.
  27. ** 8) When you close the window, the TextEdit record is disposed of.
  28. **    (This automatic disposal can easily be defeated.)
  29. **
  30. **
  31. ** To create a TextEdit control, you only need a single call.  For example:
  32. **
  33. **    CTENew(rViewCtl,            Resource ID of view control for TextEdit control.
  34. **           window,                Window to hold TERecord.
  35. **           &teHndl,                Return handle for TERecord.
  36. **           &destRect,            destRect for TERecord
  37. **           &viewRect,            viewRect for TERecord
  38. **           &borderRect,            Used to frame a border.
  39. **           32000,                Max size for TERecord text.
  40. **           cteVScrollAndGrow    TERecord read-write, with vertical scroll,
  41. **                                plus space for grow box.
  42. **    );
  43. **
  44. ** If you create a TextEdit control that is read-only, you will not be able
  45. ** to edit it, of course.  There will also be no blinking caret for that
  46. ** TextEdit control.  You will be able to select text and copy to the
  47. ** clipboard, but that is all.
  48.  
  49. ** Simply create destRect, viewRect, and borderRect appropriately, and
  50. ** then call CTENew (which stands for Control TENew).  If teHndl is returned
  51. ** nil, then CTENew failed.  Otherwise, you now have a TextEdit control in
  52. ** the window.
  53. **
  54. ** NOTE: There is a TextEdit bug (no way!!) such that you may need to set the
  55. **       viewRect right edge 2 bigger than the right edge of destRect.  If you
  56. **       do not do this, then there will be some clipping on the right edge in
  57. **       some cases.  Of course, you may want this.  You may want horizontal
  58. **       scrolling, and therefore you would want the destRect substantially
  59. **       larger than the viewRect.  If you don't want horizontal scrolling,
  60. **       then you probably don't want any clipping horizontally, and therefore
  61. **       you will need to set destRect.right 2 less than viewRect.right.
  62. **
  63. **
  64. ** If the CTENew call succeeds, you then have a TextEdit control in your
  65. ** window.  It will be automatically disposed of when you close the window.
  66. ** If you don't waht this to happen, then you can detatch it from the
  67. ** view control which owns it.  To do this, you would to the following:
  68. **
  69. **  viewCtl = CTEViewFromTE(theTextEditHndl);
  70. **  if (viewCtl) SetCRefCon(viewCtl, nil);
  71. **
  72. ** The view control keeps a reference to the TextEdit record in the refCon.
  73. ** If the refCon is cleared, then the view control does nothing.  So, all that
  74. ** is needed to detatch a TextEdit record from a view control is to set the
  75. ** view control's refCon nil.  Now if you close the window, you will still
  76. ** have the TextEdit record.
  77. **
  78. **
  79. ** To remove a TextEdit control completely from a window, you make one call:
  80. **
  81. **  CTEDispose(theTextEditHndl);
  82. **
  83. ** This disposes of the TextEdit record, the view control, and any scrollbar
  84. ** controls that were created when the TextEdit control was created with
  85. ** the call CTENew.
  86. **
  87. **
  88. ** Events for TextEdit record are handled nearly automatically.  You can
  89. ** make one of 3 calls:
  90. **
  91. **  CTEClick(eventPtr);
  92. **  CTEEvent(eventPtr);
  93. **  CTEKey(eventPtr);
  94. **
  95. ** In each case, if the event was handled, true is returned.  CTEEvent simply
  96. ** calls either CTEClick or CTEKey, whichever is appropriate.
  97. **
  98. **
  99. ** Another call you will want to use is CTEEditMenu.  This is used to set the
  100. ** state of cut/copy/paste/clear for TextEdit controls.  It checks the active
  101. ** control to see if text is selected, if the control is read-only, etc.
  102. ** Based on this information, it sets cut/copy/paste/clear either active
  103. ** or inactive.  If any menu items are set active, it returns true.
  104. **
  105. **
  106. ** One more high-level call is CTEUndo().  In response to an undo menu item
  107. ** being selected by the user, just call CTEUndo(), and the edits the user
  108. ** has made will be undone.  (This includes undoing an undo.)
  109. **
  110. **
  111. ** The last high-level call is CTEClipboard.  Call it when you want to do a
  112. ** cut/copy/paste/clear for the active TextEdit control.  The value to pass
  113. ** is as follows:
  114. **
  115. **  2: cut
  116. **  3: copy
  117. **  4: paste
  118. **  5: clear
  119. **
  120. ** These are the same values you would pass to a DA for these actions.
  121. */
  122.  
  123.  
  124.  
  125. /*****************************************************************************/
  126.  
  127.  
  128.  
  129. #ifndef __TEXTEDITCONTROL__
  130. #include "TextEditControl.h"
  131. #endif
  132.  
  133. #ifndef __CONTROLS__
  134. #include <Controls.h>
  135. #endif
  136.  
  137. #ifndef __ERRORS__
  138. #include <Errors.h>
  139. #endif
  140.  
  141. #ifndef __EVENTS__
  142. #include <Events.h>
  143. #endif
  144.  
  145. #ifndef __MEMORY__
  146. #include <Memory.h>
  147. #endif
  148.  
  149. #ifndef __MENUS__
  150. #include <Menus.h>
  151. #endif
  152.  
  153. #ifndef __OSUTILS__
  154. #include <OSUtils.h>
  155. #endif
  156.  
  157. #ifndef __RESOURCES__
  158. #include <Resources.h>
  159. #endif
  160.  
  161. #ifndef __SCRAP__
  162. #include <Scrap.h>
  163. #endif
  164.  
  165.  
  166.  
  167. /*****************************************************************************/
  168.  
  169.  
  170.  
  171. typedef struct cdefRsrcJMP {
  172.     long    moveInst;
  173.     long    jsrInst;
  174.     short    jmpInst;
  175.     long    jmpAddress;
  176. } cdefRsrcJMP;
  177. typedef cdefRsrcJMP *cdefRsrcJMPPtr, **cdefRsrcJMPHndl;
  178.  
  179. typedef struct CTEDataRec {
  180.     short    maxTextLen;
  181.     Boolean    newUndo;
  182.     short    undoSelStart;
  183.     short    undoSelEnd;
  184.     Handle    undoText;
  185.     short    mode;
  186. } CTEDataRec;
  187. typedef CTEDataRec *CTEDataPtr, **CTEDataHndl;
  188.  
  189.  
  190.  
  191. /*****************************************************************************/
  192.  
  193.  
  194.  
  195. static pascal long    CTECtl(short varCode, ControlHandle ctl, short msg, long parm);
  196. static short        theViewID;
  197.  
  198.  
  199.  
  200. /*****************************************************************************/
  201.  
  202.  
  203.  
  204. static TEHandle            gActiveTEHndl;
  205.     /* Currently active TextEdit record.  (nil if none active.) */
  206.  
  207. static TEHandle            gFoundTEHndl;
  208.     /* Global value used to return info from the TextEdit control proc. */
  209.  
  210. static ControlHandle    gFoundViewCtl;
  211.     /* Global value used to return info from the TextEdit control proc. */
  212.  
  213. static pascal void        VActionProc(ControlHandle scrollCtl, short part);
  214. static pascal void        HActionProc(ControlHandle control, short part);
  215. static void                AdjustTEBottom(TEHandle teHndl);
  216. static void                AdjustScrollValues(TEHandle teHndl);
  217. static void                AdjustOneScrollValue(TEHandle teHndl, ControlHandle ctl, Boolean vert);
  218.  
  219. ClikLoopProcPtr        gDefaultClikLoop;
  220.     /* The clikLoop TextEdit wants to use.  Our custom clikLoop must call
  221.     ** this as well.  The default TextEdit clikLoop is stored here.
  222.     */
  223.  
  224. #define kTELastForWind    1
  225. #define kCrChar            13
  226.  
  227. extern ASMNOCARET();
  228. extern ASMTECLIKLOOP();
  229.  
  230. /*****************************************************************************/
  231. /*****************************************************************************/
  232.  
  233.  
  234.  
  235. /* Activate this TextEdit record.  If another is currently active, deactivate
  236. ** that one.  The view control for this TextEdit record is also flagged to
  237. ** indicate which was the last active one for this window.  If the previous
  238. ** active TextEdit record was in the same window, then flag the old one off
  239. ** for this window.  The whole point for this per-window flagging is so that
  240. ** activate events can reactivate the correct TextEdit control per window.
  241. */
  242.  
  243. #pragma segment Controls
  244. void    CTEActivate(TEHandle teHndl)
  245. {
  246.     WindowPtr        window, oldPort;
  247.     ControlHandle    viewCtl;
  248.     TEHandle        te;
  249.  
  250.     if (teHndl != gActiveTEHndl) CTEDeactivate();
  251.         /* If the soon-to-be active TextEdit control is different than the
  252.         ** old one, deactivate the old one.  CTEDeactivate checks for the
  253.         ** case that we don't have an old one. */
  254.  
  255.     if (!teHndl) return;
  256.         /* If the soon-to-be active TextEdit control is nil, then we are
  257.         ** done.  This is another way to have no active TextEdit control.
  258.         */
  259.  
  260.     window = (WindowPtr)(*teHndl)->inPort;
  261.     for (viewCtl = nil;;) {
  262.         viewCtl = CTENext(window, &te, viewCtl);
  263.         if (!viewCtl) break;
  264.         SetCtlValue(viewCtl, false);
  265.     }        /* Turn off the last-active flag for all TextEdit controls that
  266.             ** are in this window.
  267.             */
  268.  
  269.     gActiveTEHndl = teHndl;
  270.     viewCtl = CTEViewFromTE(teHndl);
  271.  
  272.     if (viewCtl) {
  273.         SetCtlValue(viewCtl, true);
  274.             /* Set last-active-for-this-window flag for the new one. */
  275.         GetPort(&oldPort);
  276.         SetPort(window);
  277.         TEActivate(teHndl);
  278.         SetPort(oldPort);
  279.             /* Let TextEdit know that it is supposed to be active. */
  280.     }
  281. }
  282.  
  283.  
  284.  
  285. /*****************************************************************************/
  286.  
  287.  
  288.  
  289. /* This is called when a mouseDown occurs in the content of a window.  It
  290. ** returns true if the mouseDown caused a TextEdit action to occur.  Events
  291. ** that are handled include if the user clicks on a scrollbar that is
  292. ** associated with a TextEdit control.
  293. */
  294.  
  295. #pragma segment Controls
  296. Boolean    CTEClick(EventRecord *event)
  297. {
  298.     WindowPtr        oldPort, window;
  299.     Point            mouseLoc;
  300.     TEHandle        te, realActiveTEHndl;
  301.     ControlHandle    ctlHit, viewCtl;
  302.     CTEDataHndl        teData;
  303.     Boolean            vert;
  304.     short            extendSelect, part, value;
  305.  
  306.     GetPort(&oldPort);
  307.     SetPort(window = FrontWindow());
  308.     mouseLoc = event->where;
  309.     GlobalToLocal(&mouseLoc);
  310.  
  311.     if (CTEFind(window, event, &te, &ctlHit)) {
  312.             /* See if the user clicked directly on the view control for a
  313.             ** TextEdit record.  If so, we definitely have some work to do.
  314.             */
  315.         if (te != gActiveTEHndl) {
  316.             CTEActivate(te);
  317.             return(true);
  318.                 /* If user clicked on TextEdit control other than the
  319.                 ** currently active control, then activate it.  This is our
  320.                 ** only action in this case.
  321.                 */
  322.         }
  323.         extendSelect = ((event->modifiers & shiftKey) != 0);
  324.             /* Extend-select may be occuring. */
  325.         TEClick(mouseLoc, extendSelect, te);
  326.             /* Do the extend-select thing.  Most of the work is handled by
  327.             ** TextEdit.  The only thing we have to do is to update the
  328.             ** scrollbars while the user is extending the select.  This is
  329.             ** taken care of by our custom clikLoop procedure.
  330.             */
  331.  
  332.         if (viewCtl = CTEViewFromTE(te)) {
  333.             teData = (CTEDataHndl)(*viewCtl)->contrlData;
  334.             (*teData)->newUndo = true;
  335.         }
  336.  
  337.         SetPort(oldPort);
  338.         return(true);
  339.     }
  340.  
  341. /* We didn't hit the view control for a TextEdit record, but don't give up yet.
  342. ** The user may be clicking on a related scrollbar.  Let's find out...
  343. */
  344.  
  345.     if (part = FindControl(mouseLoc, window, &ctlHit)) {
  346.             /* The user did click on a control.  But is it a scrollbar
  347.             ** for a TextEdit control?  Stay tuned...
  348.             */
  349.         te = CTEFromScroll(ctlHit, &viewCtl);
  350.  
  351.         if (te) {        /* It was a related scrollbar. */
  352.  
  353.             vert = ((*ctlHit)->contrlRect.top <= (*viewCtl)->contrlRect.top);
  354.                 /* Horizontal or vertical scroll.  Only the rect knows. */
  355.  
  356.             switch (part) {
  357.                 case inThumb:
  358.                     value = GetCtlValue(ctlHit);
  359.                     part = TrackControl(ctlHit, mouseLoc, nil);
  360.                     if (part) {
  361.                         value -= GetCtlValue(ctlHit);
  362.                             /* Value now has CHANGE in value.
  363.                             ** if value changed, scroll. */
  364.                         if (value) {
  365.                             if (vert)
  366.                                 TEScroll(0, value, te);
  367.                             else
  368.                                 TEScroll(value, 0, te);
  369.                         }
  370.                     }
  371.                     break;
  372.  
  373.                 default:
  374.                     realActiveTEHndl = gActiveTEHndl;
  375.                     gActiveTEHndl = te;
  376.                         /* This is a hack way to pass the action procedure
  377.                         ** which TextEdit record we are dealing with.
  378.                         */
  379.                     if (vert)
  380.                         TrackControl(ctlHit, mouseLoc, (ProcPtr)VActionProc);
  381.                     else
  382.                         TrackControl(ctlHit, mouseLoc, (ProcPtr)HActionProc);
  383.  
  384.                     gActiveTEHndl = realActiveTEHndl;
  385.                         /* Unhack our previous hack. */
  386.                     break;
  387.             }
  388.             SetPort(oldPort);
  389.             return(true);
  390.         }
  391.     }
  392.  
  393.     SetPort(oldPort);
  394.     return(false);
  395. }
  396.  
  397.  
  398.  
  399. /*****************************************************************************/
  400.  
  401.  
  402.  
  403. /* This is the custom clikLoop, which is called from the assembly glue code.
  404. ** This handles updating the scrollbars as the user is drag-selecting in
  405. ** the TextEdit control.
  406. */
  407.  
  408. #pragma segment Controls
  409. void    CTEClikLoop(void)
  410. {
  411.     WindowPtr        oldPort, window;
  412.     TEHandle        te;
  413.     Point            mouseLoc;
  414.     Rect            viewRct;
  415.     RgnHandle        rgn;
  416.     short            dl, dr, dt, db, lh;
  417.     long            tick;
  418.  
  419.     GetPort(&oldPort);
  420.     SetPort(window = (WindowPtr)(*gActiveTEHndl)->inPort);
  421.  
  422.     te = gActiveTEHndl;
  423.         /* This better be what the user is dragging, or we did something
  424.         ** wrong elsewhere.
  425.         */
  426.  
  427.     GetMouse(&mouseLoc);
  428.     viewRct = (*te)->viewRect;
  429.  
  430.     if (!PtInRect(mouseLoc, &viewRct)) {
  431.         /* User is outside the TextEdit area, so scrolling is happening. */
  432.  
  433.         tick = TickCount();
  434.             /* As an extra feature, there is a zone around the TextEdit
  435.             ** viewRect that the scroll will be slowed down.  This zone
  436.             ** is based on the lineHeight of the active TextEdit control.
  437.             ** If the user drags outside the viewRect further than the
  438.             ** value of lineHeight, then scrolling occurs as fast as possible.
  439.             */
  440.  
  441.         rgn = NewRgn();
  442.         GetClip(rgn);
  443.         ClipRect(&(window->portRect));
  444.             /* The clipRgn is set to protect everything outside viewRect.
  445.             ** This doesn't work very well when we want to change
  446.             ** the scrollbars.  Save the old and open it up.
  447.             */
  448.  
  449.         dl = viewRct.left - mouseLoc.h;
  450.         dr = mouseLoc.h   - viewRct.right;
  451.         dt = viewRct.top  - mouseLoc.v;
  452.         db = mouseLoc.v   - viewRct.bottom;
  453.             /* Check the delta value for each side of viewRect.  This will
  454.             ** be used to determine if we should scroll fast or slow.
  455.             */
  456.  
  457.         AdjustScrollValues(te);
  458.             /* Scroll them puppies. */
  459.  
  460.         SetClip(rgn);                                /* restore clip */
  461.         DisposeRgn(rgn);
  462.             /* Make Mr. clipRgn happy again. */
  463.  
  464.         lh = (*te)->lineHeight;
  465.         if ((dl < lh) && (dr < lh) && (dt < lh) && (db < lh))
  466.             while (TickCount() <= tick + 9);
  467.                 /* Do it really slow.  (This is important on an fx!!) */
  468.     }
  469.  
  470.     SetPort(oldPort);
  471. }
  472.  
  473.  
  474.  
  475. /*****************************************************************************/
  476.  
  477.  
  478.  
  479. /* Do the cut/copy/paste/clear operations for the currently active
  480. ** TextEdit control.  Caller assumes appropriateness of the call.  Typically,
  481. ** this routine won't be called at an inappropriate time, since the menu
  482. ** item should be enabled or disabled correctly.
  483. ** Use CTEEditMenu to set the menu items undo/cut/copy/paste/clear correctly
  484. ** for the active TextEdit control.  Since undo isn't currently supported,
  485. ** all that CTEEditMenu does for the undo case is to deactivate it right now.
  486. */
  487.  
  488. #pragma segment Controls
  489. void    CTEClipboard(short menuID)
  490. {
  491.     WindowPtr        oldPort;
  492.     TEHandle        te;
  493.     ControlHandle    viewCtl;
  494.     short            maxTextLen, charsToAdd;
  495.  
  496.     if (!(te = gActiveTEHndl)) return;
  497.  
  498.     GetPort(&oldPort);
  499.     SetPort((*te)->inPort);
  500.     viewCtl = CTEViewFromTE(te);
  501.     switch (menuID) {
  502.         case 2:
  503.             CTENewUndo(viewCtl, true);
  504.             TECut(te);
  505.             ZeroScrap();
  506.             if (TEToScrap()) ZeroScrap();
  507.             break;
  508.         case 3:
  509.             TECopy(te);
  510.             ZeroScrap();
  511.             if (TEToScrap()) ZeroScrap();
  512.             break;
  513.         case 4:
  514.             TEFromScrap();
  515.             if (viewCtl) {
  516.                 maxTextLen = (*(CTEDataHndl)((*viewCtl)->contrlData))->maxTextLen;
  517.                 charsToAdd = TEGetScrapLen() - ((*te)->selEnd - (*te)->selStart);
  518.                 if ((*te)->teLength + charsToAdd <= maxTextLen) {
  519.                     CTENewUndo(viewCtl, true);
  520.                     TEPaste(te);
  521.                 }
  522.             }
  523.             break;
  524.         case 5:
  525.             CTENewUndo(viewCtl, true);
  526.             TEDelete(te);
  527.             break;
  528.     }
  529.  
  530.     AdjustTEBottom(te);
  531.     AdjustScrollValues(te);
  532.     SetPort(oldPort);
  533. }
  534.  
  535.  
  536.  
  537. /*****************************************************************************/
  538.  
  539.  
  540.  
  541. #pragma segment Controls
  542. pascal long    CTECtl(short varCode, ControlHandle ctl, short msg, long parm)
  543. {
  544. #pragma unused (varCode)
  545.  
  546.     Rect            viewRct;
  547.     TEHandle        te;
  548.     CTEDataHndl        teData;
  549.     ControlHandle    scrollCtl;
  550.     short            i;
  551.  
  552.     if (te = (TEHandle)GetCRefCon(ctl)) viewRct = (*te)->viewRect;
  553.     else SetRect(&viewRct, 0, 0, 0, 0);
  554.  
  555.     switch (msg) {
  556.         case drawCntl:
  557.             CTEUpdate(te, ctl);
  558.             break;
  559.  
  560.         case testCntl:
  561.             if (PtInRect(*(Point *)&parm, &viewRct)) {
  562.                 gFoundViewCtl = ctl;
  563.                 gFoundTEHndl  = te;
  564.                 return(1);
  565.             }
  566.             return(0);
  567.             break;
  568.  
  569.         case calcCRgns:
  570.         case calcCntlRgn:
  571.             if (msg == calcCRgns) parm &= 0x00FFFFFF;
  572.             RectRgn((RgnHandle)parm, &viewRct);
  573.             break;
  574.  
  575.         case initCntl:
  576.             break;
  577.  
  578.         case dispCntl:
  579.             if (te) {
  580.                 if (te == gActiveTEHndl) gActiveTEHndl = nil;
  581.                 TEDispose(te);
  582.                 if (teData = (CTEDataHndl)(*ctl)->contrlData) {
  583.                     if ((*teData)->undoText) DisposHandle((Handle)(*teData)->undoText);
  584.                     DisposHandle((Handle)teData);
  585.                 }
  586.                 for (i = 0; i < 2; ++i)
  587.                     if (scrollCtl = CTEScrollFromView(ctl, i))
  588.                         DisposeControl(scrollCtl);
  589.             }
  590.             break;
  591.  
  592.         case posCntl:
  593.             break;
  594.  
  595.         case thumbCntl:
  596.             break;
  597.  
  598.         case dragCntl:
  599.             break;
  600.  
  601.         case autoTrack:
  602.             break;
  603.     }
  604.  
  605.     return(0);
  606. }
  607.  
  608.  
  609.  
  610. /*****************************************************************************/
  611.  
  612.  
  613.  
  614. #pragma segment Controls
  615. void    CTEDeactivate(void)
  616. {
  617.     WindowPtr    oldPort;
  618.  
  619.     if (gActiveTEHndl) {    /* If we have an active TextEdit control... */
  620.         GetPort(&oldPort);
  621.         SetPort((WindowPtr)(*gActiveTEHndl)->inPort);
  622.         TEDeactivate(gActiveTEHndl);
  623.         SetPort(oldPort);
  624.         gActiveTEHndl = nil;    /* We don't have one anymore. */
  625.     }
  626. }
  627.  
  628.  
  629.  
  630. /*****************************************************************************/
  631.  
  632.  
  633.  
  634. #pragma segment Controls
  635. void    CTEDispose(TEHandle teHndl)
  636. {
  637.     WindowPtr    oldPort;
  638.  
  639.     GetPort(&oldPort);
  640.     SetPort((*teHndl)->inPort);
  641.     TEDispose(CTEDisposeView(CTEViewFromTE(teHndl)));
  642.         /* Dispose of the TextEdit control completely.  This includes
  643.         ** scrollbars, as well as the TextEdit view control.
  644.         */
  645.     SetPort(oldPort);
  646. }
  647.  
  648.  
  649.  
  650. /*****************************************************************************/
  651.  
  652.  
  653.  
  654. /* Dispose of the view control and related scrollbars.  This function also
  655. ** returns the handle to the TextEdit record, since it was just orphaned.
  656. ** Use this function if you want to get rid of a TextEdit control, but you
  657. ** want to keep the TextEdit record.
  658. */
  659.  
  660. #pragma segment Controls
  661. TEHandle    CTEDisposeView(ControlHandle viewCtl)
  662. {
  663.     TEHandle        te;
  664.     short            vert;
  665.     ControlHandle    ctl;
  666.  
  667.     te = (TEHandle)GetCRefCon(viewCtl);
  668.     SetCRefCon(viewCtl, nil);
  669.  
  670.     for (vert = 0; vert < 2; ++vert)
  671.         if (ctl = CTEScrollFromView(viewCtl, vert))
  672.             DisposeControl(ctl);
  673.  
  674.     DisposeControl(viewCtl);
  675.     return(te);
  676. }
  677.  
  678.  
  679.  
  680. /*****************************************************************************/
  681.  
  682.  
  683.  
  684. #pragma segment Controls
  685. short    CTEDocHeight(TEHandle teHndl)
  686. {
  687.     return(CTENumTextLines(teHndl) * (*teHndl)->lineHeight);
  688. }
  689.  
  690.  
  691.  
  692. /*****************************************************************************/
  693.  
  694.  
  695.  
  696. /* Enable or disable edit menu items based on the active TextEdit control.
  697. ** You pass the menu ID of the undo item in undoID, and the menu ID of the
  698. ** cut item in cutID.  If undoID or cutID is non-zero, then some action is
  699. ** performed.  Since I don't support TextEdit control undo yet (next version?),
  700. ** all that passing a non-zero value for undoID does is disable the undo
  701. ** menu item.  If you pass a non-zero value for cutID, then the other menu
  702. ** items cut/copy/paste/clear are updated to reflect the status of the
  703. ** active TextEdit control.
  704. */
  705.  
  706. #pragma segment Controls
  707. Boolean    CTEEditMenu(Boolean *activeItem, short editMenu, short undoID, short cutID)
  708. {
  709.     TEHandle        te;
  710.     MenuHandle        menu;
  711.     Boolean            active;
  712.     ControlHandle    viewCtl;
  713.     CTEDataHndl        teData;
  714.  
  715.     *activeItem = active = false;
  716.     menu = GetMHandle(editMenu);
  717.  
  718.     if (undoID)
  719.         DisableItem(menu, undoID);
  720.     if (cutID) {
  721.         DisableItem(menu, cutID);            /* Disable cut. */
  722.         DisableItem(menu, cutID + 1);        /* Disable copy. */
  723.         DisableItem(menu, cutID + 2);        /* Disable paste. */
  724.         DisableItem(menu, cutID + 3);        /* Disable clear. */
  725.     }
  726.  
  727.     if (!(te = gActiveTEHndl)) return(false);
  728.  
  729.     if (undoID) {
  730.         if (viewCtl = CTEViewFromTE(te)) {
  731.             teData = (CTEDataHndl)(*viewCtl)->contrlData;
  732.             if ((*teData)->undoText) {
  733.                 EnableItem(menu, undoID);
  734.                 active = true;
  735.             }
  736.         }
  737.     }
  738.  
  739.     if (cutID) {
  740.         if ((*te)->selStart != (*te)->selEnd) {
  741.             if (!CTEReadOnly(te)) {
  742.                 EnableItem(menu, cutID);        /* Enable cut. */
  743.                 EnableItem(menu, cutID + 3);    /* Enable clear. */
  744.             }
  745.             active = true;
  746.             EnableItem(menu, cutID + 1);        /* Enable copy. */
  747.         }
  748.         if (!CTEReadOnly(te)) {
  749.             TEFromScrap();
  750.             if (TEGetScrapLen()) {
  751.                 active = true;
  752.                 EnableItem(menu, cutID + 2);        /* Enable paste. */
  753.             }
  754.         }
  755.     }
  756.  
  757.     *activeItem = active;
  758.     return(true);
  759. }
  760.  
  761.  
  762.  
  763. /*****************************************************************************/
  764.  
  765.  
  766.  
  767. /* Handle the event if it applies to the active TextEdit control.  If some
  768. ** action occured due to the event, return true.
  769. */
  770.  
  771. #pragma segment Controls
  772. Boolean    CTEEvent(EventRecord *event)
  773. {
  774.     WindowPtr    window;
  775.  
  776.     switch(event->what) {
  777.  
  778.         case mouseDown:
  779.             if (FindWindow(event->where, &window) == inContent)
  780.                 if (window == FrontWindow())
  781.                     return(CTEClick(event));
  782.             break;
  783.  
  784.         case autoKey:
  785.         case keyDown:
  786.             if (!(event->modifiers & cmdKey))
  787.                 return(CTEKey(event));
  788.             break;
  789.     }
  790.  
  791.     return(false);
  792. }
  793.  
  794.  
  795.  
  796. /*****************************************************************************/
  797.  
  798.  
  799.  
  800. /* This determines if a TextEdit control was clicked on directly.  This does
  801. ** not determine if a related scrollbar was clicked on.  If a TextEdit
  802. ** control was clicked on, then true is returned, as well as the TextEdit
  803. ** handle and the handle to the view control.
  804. */
  805.  
  806. #pragma segment Controls
  807. Boolean    CTEFind(WindowPtr window, EventRecord *event,
  808.                 TEHandle *teHndl, ControlHandle *ctlHit)
  809. {
  810.     WindowPtr        oldPort;
  811.     Point            mouseLoc;
  812.  
  813.     GetPort(&oldPort);
  814.     SetPort(window);
  815.     mouseLoc = event->where;
  816.     GlobalToLocal(&mouseLoc);
  817.     SetPort(oldPort);
  818.  
  819.     gFoundTEHndl = nil;
  820.     FindControl(mouseLoc, window, ctlHit);
  821.     if (*teHndl = gFoundTEHndl) return(true);
  822.  
  823.     *ctlHit = nil;
  824.     return(false);
  825. }
  826.  
  827.  
  828.  
  829. /*****************************************************************************/
  830.  
  831.  
  832.  
  833. /* Find the TextEdit record that is related to the indicated scrollbar. */
  834.  
  835. #pragma segment Controls
  836. TEHandle    CTEFromScroll(ControlHandle scrollCtl, ControlHandle *retCtl)
  837. {
  838.     WindowPtr        window;
  839.     ControlHandle    viewCtl;
  840.     TEHandle        te;
  841.  
  842.     window = (*scrollCtl)->contrlOwner;
  843.  
  844.     for (*retCtl = viewCtl = nil;;) {
  845.         viewCtl = CTENext(window, &te, viewCtl);
  846.         if (!viewCtl) return(nil);
  847.         if (viewCtl == (ControlHandle)GetCRefCon(scrollCtl)) {
  848.             *retCtl = viewCtl;
  849.             return(te);
  850.         }
  851.     }
  852. }
  853.  
  854.  
  855.  
  856. /*****************************************************************************/
  857.  
  858.  
  859.  
  860. #pragma segment Controls
  861. void    CTEHide(TEHandle teHndl)
  862. {
  863.     ControlHandle    viewCtl, scrollCtl;
  864.     short            i;
  865.  
  866.     if (teHndl == gActiveTEHndl) CTEDeactivate();
  867.     viewCtl = CTEViewFromTE(teHndl);
  868.     if (viewCtl) {
  869.         HideControl(viewCtl);
  870.         for (i = 0; i < 2; i++) {
  871.             scrollCtl = CTEScrollFromView(viewCtl, i);
  872.             if (scrollCtl) HideControl(scrollCtl);
  873.         }
  874.     }
  875. }
  876.  
  877.  
  878.  
  879. /*****************************************************************************/
  880.  
  881.  
  882.  
  883. /* Blink the caret in the active TextEdit control.  The active TextEdit
  884. ** control may be read-only, in which case the caret does not blink. */
  885.  
  886. #pragma segment Controls
  887. void    CTEIdle(void)
  888. {
  889.     WindowPtr    window;
  890.  
  891.     if (gActiveTEHndl)
  892.         if (window = FrontWindow())
  893.             if (((WindowPeek)window)->windowKind >= userKind)
  894.                 TEIdle(gActiveTEHndl);
  895. }
  896.  
  897.  
  898.  
  899. /*****************************************************************************/
  900.  
  901.  
  902.  
  903. /* See if the keypress event applies to the TextEdit control, and if it does,
  904. ** handle it and return true.
  905. */
  906.  
  907. #pragma segment Controls
  908. Boolean    CTEKey(EventRecord *event)
  909. {
  910.     TEHandle        te;
  911.     ControlHandle    viewCtl;
  912.     short            maxTextLen;
  913.     char            key;
  914.     CTEDataHndl        teData;
  915.  
  916.     if (!(te = gActiveTEHndl))          return(false);
  917.     if (CTEReadOnly(te))                return(false);
  918.     if (!(viewCtl = CTEViewFromTE(te))) return(false);
  919.  
  920.     teData     = (CTEDataHndl)(*viewCtl)->contrlData;
  921.     maxTextLen = (*teData)->maxTextLen;
  922.     key        = event->message & charCodeMask;
  923.  
  924.     if (
  925.         ((*te)->selStart != (*te)->selEnd) ||
  926.         (key == 8) ||
  927.         ((*te)->teLength < maxTextLen)
  928.     ) {
  929.         CTENewUndo(viewCtl, false);
  930.         TEKey(key, te);
  931.         AdjustTEBottom(te);
  932.         AdjustScrollValues(te);
  933.     }
  934.  
  935.     return(true);
  936. }
  937.  
  938.  
  939.  
  940. /*****************************************************************************/
  941.  
  942.  
  943.  
  944. /* This function is used to move a TextEdit control.  Pass it the TextEdit
  945. ** record to move, plus the new position.  It will move the TextEdit control,
  946. ** along with any scrollbars the control may have.  All areas that need
  947. ** updating are cleared and invalidated.
  948. */
  949.  
  950. #pragma segment Controls
  951. void    CTEMove(TEHandle teHndl, short newH, short newV)
  952. {
  953.     WindowPtr        oldPort;
  954.     Rect            viewRct, rct;
  955.     short            i, dx, dy, mode;
  956.     ControlHandle    viewCtl, ctl;
  957.     CTEDataHndl        teData;
  958.  
  959.     if (!(viewCtl = CTEViewFromTE(teHndl))) return;
  960.  
  961.     GetPort(&oldPort);
  962.     SetPort((*teHndl)->inPort);
  963.  
  964.     viewRct = (*viewCtl)->contrlRect;
  965.     EraseRect(&viewRct);
  966.  
  967.     dx = newH - viewRct.left;
  968.     dy = newV - viewRct.top;
  969.  
  970.     for (i = 0; i < 2; ++i) {
  971.         if (ctl = CTEScrollFromView(viewCtl, i)) {
  972.             rct = (*ctl)->contrlRect;
  973.             MoveControl(ctl, rct.left + dx, rct.top + dy);
  974.         }
  975.     }
  976.  
  977.     OffsetRect(&(*viewCtl)->contrlRect, dx, dy);
  978.     OffsetRect(&(*teHndl)->destRect, dx, dy);
  979.     OffsetRect(&(*teHndl)->viewRect, dx, dy);
  980.  
  981.     rct = viewRct = (*viewCtl)->contrlRect;
  982.     InvalRect(&viewRct);
  983.  
  984.     teData = (CTEDataHndl)(*viewCtl)->contrlData;
  985.     mode   = (*teData)->mode;
  986.     rct.top  = rct.bottom - 16;
  987.     rct.left = rct.right - 16;
  988.  
  989.     if (mode & (cteVScrollAndGrow - cteVScroll + cteHScrollAndGrow - cteHScroll))
  990.         EraseRect(&rct);
  991.  
  992.     if (mode & (cteVScrollAndGrow - cteVScroll)) OffsetRect(&rct, 16, 0);
  993.     if (mode & (cteHScrollAndGrow - cteHScroll)) OffsetRect(&rct, 0, 16);
  994.     InvalRect(&rct);
  995.  
  996.     SetPort(oldPort);
  997. }
  998.  
  999.  
  1000.  
  1001. /*****************************************************************************/
  1002.  
  1003.  
  1004.  
  1005. /* Create a new TextEdit control.  See the comments at the beginning of this
  1006. ** file for more information.
  1007. */
  1008.  
  1009. #pragma segment Controls
  1010. void    CTENew(short viewID, WindowPtr window, TEHandle *teHndl, Rect *dRect,
  1011.                Rect *vRect, Rect *bRect, short maxTextLen, short mode)
  1012. {
  1013.     Rect            destRect, viewRect, brdrRect;
  1014.     WindowPtr        oldPort;
  1015.     TEHandle        te;
  1016.     Boolean            err;
  1017.     short            width, height;
  1018.     Rect            ctlRect;
  1019.     ControlHandle    viewCtl, hScrollCtl, vScrollCtl;
  1020.     cdefRsrcJMPHndl    cdefRsrc;
  1021.     CTEDataHndl        teData;
  1022.     
  1023.     theViewID = viewID;        /* Keep viewID that was passed in. */
  1024.  
  1025.     GetPort(&oldPort);
  1026.     SetPort(window);
  1027.  
  1028.     destRect = *dRect;
  1029.     viewRect = *vRect;
  1030.     brdrRect = *bRect;
  1031.         /* Make sure that the rects are not in memory that may move. */
  1032.  
  1033.     te = TENew(&destRect, &viewRect);
  1034.         /* Do the main thing. */
  1035.  
  1036.     err = false;
  1037.     viewCtl = hScrollCtl = vScrollCtl = nil;
  1038.         /* Prepare for various failures. */
  1039.  
  1040.     if (te) {        /* If we were able to create the TextEdit record... */
  1041.  
  1042.         TEAutoView(true, te);
  1043.             /* Let TextEdit 3.0 do most of the scrolling work. */
  1044.         gDefaultClikLoop = (*te)->clikLoop;
  1045.         (*te)->clikLoop  = (ClikLoopProcPtr) ASMTECLIKLOOP;
  1046.             /* We will do the remainder of the work. */
  1047.  
  1048.         cdefRsrc = (cdefRsrcJMPHndl)GetResource('CDEF', viewID);
  1049.         (*cdefRsrc)->jmpAddress = (long)CTECtl;
  1050.         viewCtl = NewControl(window, &brdrRect, nil, true, 0, 0, 0,
  1051.                              viewID * 16, (long)te);
  1052.             /* Use our custom view cdef.  It's wierd, but it's small. */
  1053.  
  1054.         if (!viewCtl) err = true;
  1055.         else {
  1056.             (*viewCtl)->contrlData = nil;
  1057.             if (teData = (CTEDataHndl)NewHandle(sizeof(CTEDataRec))) {
  1058.                 (*teData)->maxTextLen  = maxTextLen;
  1059.                 (*teData)->undoText    = nil;
  1060.                 (*teData)->mode        = mode;
  1061.                 (*viewCtl)->contrlData = (Handle)teData;
  1062.             }
  1063.             else err = true;
  1064.  
  1065.             if (mode & cteHScroll) {        /* Caller wants a horizontal scrollbar... */
  1066.                 SetRect(&ctlRect, 0, 0, 100, 16);
  1067.                 hScrollCtl = NewControl(window, &ctlRect, nil, true, 0, 0, 0,
  1068.                                         scrollBarProc, (long)viewCtl);
  1069.                 if (hScrollCtl) {
  1070.                     MoveControl(hScrollCtl, brdrRect.left, brdrRect.bottom - 1);
  1071.                     width = brdrRect.right - brdrRect.left;
  1072.                     if (mode & (cteHScrollAndGrow - cteHScroll))
  1073.                         if (!(mode & cteVScroll)) width -= 15;
  1074.                     SizeControl(hScrollCtl, width, 16);
  1075.                         /* Line the scrollbar up with the borderRect. */
  1076.                 }
  1077.                 else err = true;
  1078.             }
  1079.  
  1080.             if (mode & cteVScroll) {        /* Caller wants a vertical scrollbar... */
  1081.                 SetRect(&ctlRect, 0, 0, 16, 100);
  1082.                 vScrollCtl = NewControl(window, &ctlRect, nil, true, 0, 0, 0,
  1083.                                         scrollBarProc, (long)viewCtl);
  1084.                 if (vScrollCtl) {
  1085.                     MoveControl(vScrollCtl, brdrRect.right - 1, brdrRect.top);
  1086.                     height = brdrRect.bottom - brdrRect.top;
  1087.                     if (mode & (cteVScrollAndGrow - cteVScroll))
  1088.                         if (!(mode & cteHScroll)) height -= 15;
  1089.                     SizeControl(vScrollCtl, 16, height);
  1090.                         /* Line the scrollbar up with the borderRect. */
  1091.                 }
  1092.                 else err = true;
  1093.             }
  1094.         }
  1095.     }
  1096.     else err = true;
  1097.  
  1098.     SetPort(oldPort);
  1099.  
  1100.     if (err) {        /* Oops.  Somebody wasn't happy. */
  1101.         if (viewCtl)
  1102.             DisposeControl(viewCtl);
  1103.                 /* This also disposes of TextEdit handle! */
  1104.         else
  1105.             if (te) TEDispose(te);
  1106.                 /* We have to dispose of the TextEdit handle ourselves if
  1107.                 ** creating the view control failed. */
  1108.  
  1109.         te = nil;        /* Return that there is no TextEdit control. */
  1110.  
  1111.         if (hScrollCtl)
  1112.             DisposeControl(hScrollCtl);
  1113.                 /* More clean-up. */
  1114.  
  1115.         if (vScrollCtl)
  1116.             DisposeControl(vScrollCtl);
  1117.                 /* And still more clean-up. */
  1118.     }
  1119.     else {
  1120.         if (mode & cteReadOnly)
  1121.             (*te)->caretHook = (ProcPtr)ASMNOCARET;
  1122.                 /* If read-only, then disable caret for this
  1123.                 ** TextEdit control.
  1124.                 */
  1125.         CTEActivate(te);
  1126.     }
  1127.  
  1128.     AdjustScrollValues(*teHndl = te);
  1129.         /* Give the scrollbars an initial value.  This is because the
  1130.         ** TextEdit control could have been created with
  1131.         ** destRect.top < viewRect.top or destRect.left < viewRect.left.
  1132.         ** I don't know why anyone would want to, but it is legal.
  1133.         */
  1134. }
  1135.  
  1136.  
  1137.  
  1138. /*****************************************************************************/
  1139.  
  1140.  
  1141.  
  1142. /* Save the data (if appropriate) so that user can undo. */
  1143.  
  1144. #pragma segment Controls
  1145. void    CTENewUndo(ControlHandle viewCtl, Boolean alwaysNewUndo)
  1146. {
  1147.     TEHandle    teHndl;
  1148.     CTEDataHndl    teData;
  1149.     Handle        hText, uText;
  1150.  
  1151.     if (!viewCtl) return;
  1152.  
  1153.     teHndl = (TEHandle)GetCRefCon(viewCtl);
  1154.     teData = (CTEDataHndl)(*viewCtl)->contrlData;
  1155.  
  1156.     hText = (*teHndl)->hText;
  1157.     uText = (*teData)->undoText;
  1158.         /* hText is text in the TextEdit record that is being edited.  */
  1159.         /* uText is the undo data (if any) prior to current edit task. */
  1160.  
  1161.     if (!uText) {                /* No undo text (therefore no editing) yet. */
  1162.         uText = NewHandle((*teHndl)->teLength);
  1163.         if (!uText) return;        /* Not enough memory to support undo. */
  1164.         (*teData)->undoText = uText;
  1165.         alwaysNewUndo = true;
  1166.             /* Since this is our first edit, we will want to cache the
  1167.             ** data, no matter for what reason we were called. */
  1168.     }
  1169.  
  1170.     if ((alwaysNewUndo) || ((*teData)->newUndo)) {
  1171.         SetHandleSize(uText, (*teHndl)->teLength);
  1172.         if (MemError()) return;
  1173.             /* Not enough memory to handle this undo. */
  1174.         BlockMove(*hText, *uText, (*teHndl)->teLength);
  1175.         (*teData)->newUndo      = false;
  1176.         (*teData)->undoSelStart = (*teHndl)->selStart;
  1177.         (*teData)->undoSelEnd   = (*teHndl)->selEnd;
  1178.     }
  1179. }
  1180.  
  1181.  
  1182.  
  1183. /*****************************************************************************/
  1184.  
  1185.  
  1186.  
  1187. /* Get the next TextEdit control in the window.  You pass it a control handle
  1188. ** for the view control, or nil to start at the beginning of the window.
  1189. ** It returns both a TextEdit handle and the view control handle for that
  1190. ** TextEdit record.  If none is found, nil is returned.  This allows you to
  1191. ** repeatedly call this function and walk through all the TextEdit controls
  1192. ** in a window.
  1193. */
  1194.  
  1195. #pragma segment Controls
  1196. ControlHandle    CTENext(WindowPtr window, TEHandle *teHndl, ControlHandle ctl)
  1197. {
  1198.     short    defProcID;
  1199.     ResType    defProcType;
  1200.     Str255    defProcName;
  1201.  
  1202.     *teHndl = nil;
  1203.  
  1204.     if (!ctl)
  1205.         ctl = ((WindowPeek)window)->controlList;
  1206.     else
  1207.         ctl = (*ctl)->nextControl;
  1208.  
  1209.     while (ctl) {
  1210.         defProcID = !theViewID;
  1211.         GetResInfo((*ctl)->contrlDefProc, &defProcID, &defProcType, defProcName);
  1212.         if (defProcID == theViewID) {
  1213.             *teHndl = (TEHandle)GetCRefCon(ctl);
  1214.             break;
  1215.         }
  1216.         ctl = (*ctl)->nextControl;
  1217.     }
  1218.  
  1219.     return(ctl);
  1220. }
  1221.  
  1222.  
  1223.  
  1224. /*****************************************************************************/
  1225.  
  1226.  
  1227.  
  1228. /* Return the number of lines of text.  This is because there is a bug in
  1229. ** TextEdit where the number of lines returned is incorrect if the text
  1230. ** ends with a c/r.  This function adjusts for this bug.
  1231. */
  1232.  
  1233. #pragma segment Controls
  1234. short    CTENumTextLines(TEHandle teHndl)
  1235. {
  1236.     short    lines;
  1237.     char    *cptr;
  1238.  
  1239.     lines = (*teHndl)->nLines;
  1240.  
  1241.     cptr = *((*teHndl)->hText);        /* Pointer to first TextEdit character. */
  1242.     if (cptr[(*teHndl)->teLength - 1] == kCrChar) ++lines;
  1243.         /* Since nLines isn’t right if the last character is a return,
  1244.         ** check for that case and fix it.
  1245.         */
  1246.  
  1247.     return(lines);
  1248. }
  1249.  
  1250.  
  1251.  
  1252. /*****************************************************************************/
  1253.  
  1254.  
  1255.  
  1256. /* Return the number of text lines in the view area. */
  1257.  
  1258. #pragma segment Controls
  1259. short    CTENumViewLines(TEHandle teHndl)
  1260. {
  1261.     short    viewHeight;
  1262.  
  1263.     viewHeight = (*teHndl)->viewRect.bottom - (*teHndl)->viewRect.top;
  1264.     return(viewHeight / (*teHndl)->lineHeight);
  1265. }
  1266.  
  1267.  
  1268.  
  1269. /*****************************************************************************/
  1270.  
  1271.  
  1272.  
  1273. /* Use this function to print the contents of a TextEdit record.  Pass it a
  1274. ** TextEdit handle, a pointer to a text offset, and a pointer to a rect to
  1275. ** print the text in.  The offset should be initialized to what character
  1276. ** in the TextEdit record you wish to start printing at (most likely 0).
  1277. ** The print function prints as much text as will fit in the rect, and
  1278. ** then updates the offset to tell you what is the first character that didn't
  1279. ** print.  You can then call the print function again with another rect with
  1280. ** this new offset, and it will print the text starting at the new offset.
  1281. ** This method is very useful when a single TextEdit record is longer than a
  1282. ** single page, and you wish the text to break at the end of the page.
  1283. ** The bottom of rect is also updated, along with the offset.  The bottom edge
  1284. ** of the rect is changed to reflect the actual bottom of the text printed.
  1285. ** This is useful because the rect passed in didn't necessarily hold an
  1286. ** integer number of lines of text.  The bottom of the rect is adjusted so
  1287. ** it exactly holds complete lines of text.
  1288. ** It is also possible that the rect could hold substantially more lines of
  1289. ** text than there are remaining.  Again, in this situation, the bottom of
  1290. ** rect is adjusted so that the rect tightly bounds the text printed.
  1291. ** The remaining piece of information passed back is an indicator that the
  1292. ** text through the end of the TextEdit record was printed.  When the end
  1293. ** of the text is reached, the offset for the next text to be printed is
  1294. ** returned as -1.  This indicates that processing of the TextEdit record
  1295. ** is complete.
  1296. */
  1297.  
  1298. #pragma segment Controls
  1299. OSErr    CTEPrint(TEHandle teHndl, short *teOffset, Rect *teRct)
  1300. {
  1301.     short        len, offset, numLines, rctHeight, rctLines, h;
  1302.     Handle        hText, keepHText;
  1303.     Rect        keepDestRect, keepViewRect;
  1304.     WindowPtr    tePort, printPort;
  1305.  
  1306.     len = (*teHndl)->teLength;
  1307.     if (!(keepHText = NewHandle(len))) return(memFullErr);
  1308.  
  1309.     if ((offset = *teOffset) >= len) {
  1310.         *teOffset = -1;        /* We are offset further than we have text. */
  1311.         return(noErr);        /* Just say that we have no more text. */
  1312.     }
  1313.  
  1314.     BlockMove(*(hText = (*teHndl)->hText), *keepHText, len);
  1315.     keepDestRect = (*teHndl)->destRect;
  1316.     keepViewRect = (*teHndl)->viewRect;
  1317.     tePort = (*teHndl)->inPort;
  1318.         /* Cache some information from the TextEdit record. */
  1319.  
  1320.     BlockMove(*hText + offset, *hText, len - offset);
  1321.     (*teHndl)->teLength = len - offset;
  1322.         /* Throw out the characters that have already been printed. */
  1323.  
  1324.     GetPort(&printPort);
  1325.     (*teHndl)->inPort = printPort;
  1326.     (*teHndl)->destRect = (*teHndl)->viewRect = *teRct;
  1327.     TECalText(teHndl);
  1328.         /* Install the print rect into the TextEdit record and then
  1329.         ** rewrap the unprinted text into this rectangle.  The text
  1330.         ** is now formatted correctly to print this rect's worth. */
  1331.  
  1332.     numLines  = CTENumTextLines(teHndl);
  1333.     rctHeight = teRct->bottom - teRct->top;
  1334.     rctLines  = rctHeight / (h = (*teHndl)->lineHeight);
  1335.  
  1336.     if (rctLines > numLines) rctLines = numLines;
  1337.     teRct->bottom = teRct->top + rctLines * h;
  1338.     (*teHndl)->destRect = (*teHndl)->viewRect = *teRct;
  1339.         /* We now have the minimum rectangle to hold as much of the text
  1340.         ** as would fit into the rectangle passed in.  The final calc on
  1341.         ** the rect prevents partial lines from being drawn. */
  1342.  
  1343.     TEUpdate(teRct, teHndl);        /* Draw this portion of the text. */
  1344.  
  1345.     if (rctLines == numLines)
  1346.         *teOffset = -1;
  1347.             /* Printed the last of the text for this record. */
  1348.     else
  1349.         *teOffset = (*teHndl)->lineStarts[rctLines] + offset;
  1350.             /* Offset to the first character not printed. */
  1351.  
  1352.     SetHandleSize(hText, len);
  1353.     BlockMove(*keepHText, *hText, len);
  1354.     DisposHandle(keepHText);
  1355.     (*teHndl)->teLength = len;
  1356.     (*teHndl)->inPort   = tePort;
  1357.     (*teHndl)->destRect = keepDestRect;
  1358.     (*teHndl)->viewRect = keepViewRect;
  1359.     TECalText(teHndl);
  1360.         /* Restore the TextEdit record to the way it was. */
  1361. }
  1362.  
  1363.  
  1364.  
  1365. /*****************************************************************************/
  1366.  
  1367.  
  1368.  
  1369. /* Return if the TextEdit control is read/write (true) or read-only (false). */
  1370.  
  1371. #pragma segment Controls
  1372. Boolean    CTEReadOnly(TEHandle teHndl)
  1373. {
  1374.     if (!teHndl) return(false);
  1375.     if ((*teHndl)->caretHook == (ProcPtr) ASMNOCARET) return(true);
  1376.     return(false);
  1377. }
  1378.  
  1379.  
  1380.  
  1381. /*****************************************************************************/
  1382.  
  1383.  
  1384.  
  1385. /* Return the control handle for the TextEdit control's scrollbar, either
  1386. ** vertical or horizontal.  If the scrollbar doesn't, nil is returned.
  1387. */
  1388.  
  1389. #pragma segment Controls
  1390. ControlHandle    CTEScrollFromTE(TEHandle teHndl, Boolean vertScroll)
  1391. {
  1392.     ControlHandle    viewCtl;
  1393.  
  1394.     viewCtl = CTEViewFromTE(teHndl);
  1395.     if (!viewCtl) return(nil);
  1396.  
  1397.     return(CTEScrollFromView(viewCtl, vertScroll));
  1398. }
  1399.  
  1400.  
  1401.  
  1402. /*****************************************************************************/
  1403.  
  1404.  
  1405.  
  1406. /* Return the control handle for the scrollbar related to the view control,
  1407. ** either horizontal or vertical.  If the scrollbar doesn't exist, return nil.
  1408. */
  1409.  
  1410. #pragma segment Controls
  1411. ControlHandle    CTEScrollFromView(ControlHandle viewCtl, Boolean vertScroll)
  1412. {
  1413.     ControlHandle    ctl;
  1414.     WindowPtr        window;
  1415.     Boolean            vert;
  1416.  
  1417.     window = (*viewCtl)->contrlOwner;
  1418.     ctl    = ((WindowPeek)window)->controlList;
  1419.  
  1420.     for (; ctl;) {
  1421.         if ((ControlHandle)GetCRefCon(ctl) == viewCtl) {
  1422.             vert = false;
  1423.             if ((*ctl)->contrlRect.right == (*ctl)->contrlRect.left + 16)
  1424.                 vert = true;
  1425.             if (vert == vertScroll) return(ctl);
  1426.         }
  1427.         ctl = (*ctl)->nextControl;
  1428.     }
  1429.     return(nil);
  1430. }
  1431.  
  1432.  
  1433.  
  1434. /*****************************************************************************/
  1435.  
  1436.  
  1437.  
  1438. /* Select a range of text.  TESetSelect can't be used alone because it doesn't
  1439. ** update the scrollbars.  This function calls TESetSelect, and then fixes up
  1440. ** the scrollbars.
  1441. */
  1442.  
  1443. #pragma segment Controls
  1444. void    CTESetSelect(short start, short end, TEHandle teHndl)
  1445. {
  1446.     WindowPtr        oldPort;
  1447.     ControlHandle    viewCtl;
  1448.     CTEDataHndl        teData;
  1449.  
  1450.     GetPort(&oldPort);
  1451.     SetPort((*teHndl)->inPort);
  1452.     TESetSelect(start, end, teHndl);
  1453.     AdjustScrollValues(teHndl);
  1454.     if (viewCtl = CTEViewFromTE(teHndl)) {
  1455.         teData = (CTEDataHndl)(*viewCtl)->contrlData;
  1456.         (*teData)->newUndo = true;
  1457.     }
  1458.     SetPort(oldPort);
  1459. }
  1460.  
  1461.  
  1462.  
  1463. /*****************************************************************************/
  1464.  
  1465.  
  1466.  
  1467. #pragma segment Controls
  1468. void    CTEShow(TEHandle teHndl)
  1469. {
  1470.     ControlHandle    viewCtl, scrollCtl;
  1471.     short            i;
  1472.  
  1473.     if (teHndl == gActiveTEHndl) CTEDeactivate();
  1474.     viewCtl = CTEViewFromTE(teHndl);
  1475.     if (viewCtl) {
  1476.         ShowControl(viewCtl);
  1477.         for (i = 0; i < 2; i++) {
  1478.             scrollCtl = CTEScrollFromView(viewCtl, i);
  1479.             if (scrollCtl) ShowControl(scrollCtl);
  1480.         }
  1481.     }
  1482. }
  1483.  
  1484.  
  1485.  
  1486. /*****************************************************************************/
  1487.  
  1488.  
  1489.  
  1490. /* This function is used to resize a TextEdit control.  Pass it the TextEdit
  1491. ** record to resize, plus the new horizontal and vertical size.  It will
  1492. ** resize the TextEdit control, realign the text, if necessary, plus it will
  1493. ** resize and adjust any scrollbars the TextEdit control may have.  All areas
  1494. ** that need updating are cleared and invalidated.
  1495. */
  1496.  
  1497. #pragma segment Controls
  1498. void    CTESize(TEHandle teHndl, short newH, short newV)
  1499. {
  1500.     WindowPtr        oldPort;
  1501.     Rect            viewRct, rct;
  1502.     short            i, dx, dy, mode;
  1503.     ControlHandle    viewCtl, ctl;
  1504.     CTEDataHndl        teData;
  1505.  
  1506.     if (!(viewCtl = CTEViewFromTE(teHndl))) return;
  1507.  
  1508.     GetPort(&oldPort);
  1509.     SetPort((*teHndl)->inPort);
  1510.  
  1511.     viewRct = (*viewCtl)->contrlRect;
  1512.     EraseRect(&viewRct);
  1513.  
  1514.     dx = newH - (viewRct.right  - viewRct.left);
  1515.     dy = newV - (viewRct.bottom - viewRct.top);
  1516.  
  1517.     for (i = 0; i < 2; ++i) {
  1518.         if (ctl = CTEScrollFromView(viewCtl, i)) {
  1519.             rct = (*ctl)->contrlRect;
  1520.             if (i) {
  1521.                 SizeControl(ctl, rct.right - rct.left, rct.bottom - rct.top + dy);
  1522.                 MoveControl(ctl, rct.left + dx, rct.top);
  1523.             }
  1524.             else {
  1525.                 SizeControl(ctl, rct.right - rct.left + dx, rct.bottom - rct.top);
  1526.                 MoveControl(ctl, rct.left, rct.top + dy);
  1527.             }
  1528.         }
  1529.     }
  1530.  
  1531.     (*viewCtl)->contrlRect.right  += dx;
  1532.     (*viewCtl)->contrlRect.bottom += dy;
  1533.     (*teHndl)->destRect.right  += dx;
  1534.     (*teHndl)->destRect.bottom += dy;
  1535.     (*teHndl)->viewRect.right  += dx;
  1536.     (*teHndl)->viewRect.bottom += dy;
  1537.  
  1538.     TECalText(teHndl);
  1539.     AdjustTEBottom(teHndl);
  1540.     AdjustScrollValues(teHndl);
  1541.  
  1542.     rct = viewRct = (*viewCtl)->contrlRect;
  1543.     InvalRect(&viewRct);
  1544.  
  1545.     teData = (CTEDataHndl)(*viewCtl)->contrlData;
  1546.     mode   = (*teData)->mode;
  1547.     rct.top  = rct.bottom - 16;
  1548.     rct.left = rct.right - 16;
  1549.  
  1550.     if (mode & (cteVScrollAndGrow - cteVScroll + cteHScrollAndGrow - cteHScroll))
  1551.         EraseRect(&rct);
  1552.  
  1553.     if (mode & (cteVScrollAndGrow - cteVScroll)) OffsetRect(&rct, 16, 0);
  1554.     if (mode & (cteHScrollAndGrow - cteHScroll)) OffsetRect(&rct, 0, 16);
  1555.     InvalRect(&rct);
  1556.  
  1557.     SetPort(oldPort);
  1558. }
  1559.  
  1560.  
  1561.  
  1562. /*****************************************************************************/
  1563.  
  1564.  
  1565.  
  1566. /* Swap the TextEdit text handle with the text handle passed in. */
  1567.  
  1568. #pragma segment Controls
  1569. Handle    CTESwapText(TEHandle teHndl, Handle newText, Boolean update)
  1570. {
  1571.     WindowPtr    oldPort;
  1572.     Handle        hText;
  1573.     Rect        destRect, viewRect;
  1574.     short        oldTextLen, newTextLen;
  1575.  
  1576.     GetPort(&oldPort);
  1577.     SetPort((*teHndl)->inPort);
  1578.  
  1579.     hText = (*teHndl)->hText;
  1580.  
  1581.     newTextLen = GetHandleSize(newText);
  1582.     oldTextLen = (*teHndl)->teLength;
  1583.     SetHandleSize(hText, oldTextLen);
  1584.         /* Just to make sure TE is behaving itself. */
  1585.  
  1586.     (*teHndl)->hText = newText;
  1587.     (*teHndl)->teLength = newTextLen;
  1588.  
  1589.     TECalText(teHndl);
  1590.  
  1591.     if (update) {
  1592.         destRect = (*teHndl)->destRect;
  1593.         viewRect = (*teHndl)->viewRect;
  1594.  
  1595.         OffsetRect(&destRect,
  1596.             viewRect.left - destRect.left,
  1597.             viewRect.top  - destRect.top);
  1598.  
  1599.         (*teHndl)->destRect = destRect;
  1600.         (*teHndl)->selStart = (*teHndl)->selEnd = 0;
  1601.  
  1602.         EraseRect(&viewRect);
  1603.         TEUpdate(&viewRect, teHndl);
  1604.     }
  1605.  
  1606.     AdjustScrollValues(teHndl);
  1607.  
  1608.     SetPort(oldPort);
  1609.     return(hText);
  1610. }
  1611.  
  1612.  
  1613.  
  1614. /*****************************************************************************/
  1615.  
  1616.  
  1617.  
  1618. /* Return information for the currently active TextEdit control.  The currently
  1619. ** active TextEdit control is stored in gActiveTEHndl, and can be accessed
  1620. ** directly.  If gActiveTEHndl is nil, then there is no currently active one.
  1621. ** The information that we return is the viewRect and window of the active
  1622. ** TextEdit control.  This is information that could be gotten directly, but
  1623. ** this call makes it a little more convenient.
  1624. */
  1625.  
  1626. #pragma segment Controls
  1627. WindowPtr    CTETargetInfo(TEHandle *teHndl, Rect *teView)
  1628. {
  1629.     SetRect(teView, 0, 0, 0, 0);
  1630.     if (!(*teHndl = gActiveTEHndl)) return(nil);
  1631.  
  1632.     *teView = (*gActiveTEHndl)->viewRect;
  1633.     return((*gActiveTEHndl)->inPort);
  1634. }
  1635.  
  1636.  
  1637.  
  1638. /*****************************************************************************/
  1639.  
  1640.  
  1641.  
  1642. #pragma segment Controls
  1643. void    CTEUndo(void)
  1644. {
  1645.     TEHandle        teHndl;
  1646.     ControlHandle    viewCtl;
  1647.     CTEDataHndl        teData;
  1648.     Handle            hText, uText;
  1649.     short            oldStart, oldEnd;
  1650.  
  1651.     if (!(teHndl = gActiveTEHndl)) return;
  1652.  
  1653.     if (viewCtl = CTEViewFromTE(teHndl)) {
  1654.         teData = (CTEDataHndl)(*viewCtl)->contrlData;
  1655.         hText  = (*teHndl)->hText;
  1656.         uText  = (*teData)->undoText;
  1657.         if (!uText) return;        /* There is no undo yet.  Getting called in
  1658.                                 ** this state is actually an error, but we
  1659.                                 ** check, just in case.
  1660.                                 */
  1661.         oldStart = (*teHndl)->selStart;
  1662.         oldEnd   = (*teHndl)->selEnd;
  1663.         (*teData)->undoText = CTESwapText(teHndl, (*teData)->undoText, false);
  1664.         (*teHndl)->selStart = (*teHndl)->selEnd = 0;
  1665.         CTEUpdate(teHndl, viewCtl);
  1666.         CTESetSelect((*teData)->undoSelStart, (*teData)->undoSelEnd, teHndl);
  1667.             /* CTESetSelect flags it as a new undo situation for us. */
  1668.         (*teData)->undoSelStart = oldStart;
  1669.         (*teData)->undoSelEnd   = oldEnd;
  1670.     }
  1671. }
  1672.  
  1673.  
  1674.  
  1675. /*****************************************************************************/
  1676.  
  1677.  
  1678.  
  1679. /* Draw the TextEdit control and frame. */
  1680.  
  1681. #pragma segment Controls
  1682. void    CTEUpdate(TEHandle teHndl, ControlHandle ctl)
  1683. {
  1684.     WindowPtr    oldPort;
  1685.     Rect        viewRect, brdrRect;
  1686.  
  1687.     if (teHndl) {
  1688.         GetPort(&oldPort);
  1689.         SetPort((*teHndl)->inPort);
  1690.         viewRect = (*teHndl)->viewRect;
  1691.         EraseRect(&viewRect);
  1692.         TEUpdate(&viewRect, teHndl);
  1693.         brdrRect = (*ctl)->contrlRect;
  1694.         FrameRect(&brdrRect);
  1695.         SetPort(oldPort);
  1696.     }
  1697. }
  1698.  
  1699.  
  1700.  
  1701. /*****************************************************************************/
  1702.  
  1703.  
  1704.  
  1705. /* Return the control handle for the view control that owns the TextEdit
  1706. ** record.  Use this to find the view to do customizations such as changing
  1707. ** the update procedure for this TextEdit control.
  1708. */
  1709.  
  1710. #pragma segment Controls
  1711. ControlHandle    CTEViewFromTE(TEHandle teHndl)
  1712. {
  1713.     WindowPtr        window;
  1714.     ControlHandle    viewCtl;
  1715.     TEHandle        te;
  1716.  
  1717.     window = (WindowPtr)(*teHndl)->inPort;
  1718.  
  1719.     for (viewCtl = nil;;) {
  1720.  
  1721.         viewCtl = CTENext(window, &te, viewCtl);
  1722.         if ((!viewCtl) || (te == teHndl)) return(viewCtl);
  1723.     }
  1724. }
  1725.  
  1726.  
  1727.  
  1728. /*****************************************************************************/
  1729.  
  1730.  
  1731.  
  1732. /* Call this when a window with TextEdit controls is being activated.  This
  1733. ** will make the TextEdit control that was last active in this window the
  1734. ** active TextEdit control again.  If it can't find one that was the last
  1735. ** active control, it makes the first one it finds the active control.
  1736. */
  1737.  
  1738. #pragma segment Controls
  1739. void    CTEWindActivate(WindowPtr window, Boolean activate)
  1740. {
  1741.     short            hilite, scrollNum;
  1742.     TEHandle        targetTE;
  1743.     ControlHandle    viewCtl, scrollCtl;
  1744.     TEHandle        te;
  1745.  
  1746.     hilite = 255;
  1747.     if (activate) hilite = 0;
  1748.  
  1749.     for (targetTE = nil, viewCtl = nil;;) {
  1750.  
  1751.         viewCtl = CTENext(window, &te, viewCtl);
  1752.         if (!viewCtl) break;
  1753.             /* The only way out of the loop. */
  1754.  
  1755.         if (GetCtlValue(viewCtl)) targetTE = te;
  1756.  
  1757.         for (scrollNum = 0; scrollNum < 2; ++scrollNum) {
  1758.             scrollCtl = CTEScrollFromTE(te, scrollNum);
  1759.             if (scrollCtl)
  1760.                 HiliteControl(scrollCtl, hilite);
  1761.         }
  1762.     }
  1763.  
  1764.     if (activate) {
  1765.         CTEActivate(targetTE);
  1766.         if (targetTE) return;
  1767.  
  1768.         viewCtl = CTENext(window, &te, nil);
  1769.         if (viewCtl) CTEActivate(te);
  1770.     }
  1771.     else
  1772.         CTEDeactivate();
  1773. }
  1774.  
  1775.  
  1776.  
  1777. /*****************************************************************************/
  1778. /*****************************************************************************/
  1779.  
  1780.  
  1781.  
  1782. #pragma segment Controls
  1783. pascal void    VActionProc(ControlHandle scrollCtl, short part)
  1784. {
  1785.     short        lineHeight, delta, value, teOffset;
  1786.     short        oldValue, max;
  1787.     TEHandle    te;
  1788.     
  1789.     if (part) {                        /* If it was actually in the control. */
  1790.  
  1791.         te = gActiveTEHndl;
  1792.         lineHeight = (*te)->lineHeight;
  1793.  
  1794.         switch (part) {
  1795.             case inUpButton:
  1796.             case inDownButton:        /* One line. */
  1797.                 delta = lineHeight;
  1798.                 break;
  1799.             case inPageUp:            /* One page. */
  1800.             case inPageDown:
  1801.                 delta = (*te)->viewRect.bottom - (*te)->viewRect.top;
  1802.                 if (delta > lineHeight) delta -= lineHeight;
  1803.                 break;
  1804.         }
  1805.         if ( (part == inUpButton) || (part == inPageUp) )
  1806.             delta = -delta;        /* Reverse direction for an upper. */
  1807.  
  1808.         value = (oldValue = GetCtlValue(scrollCtl)) + delta;
  1809.         if (value < 0) value = 0;
  1810.         if (value > (max = GetCtlMax(scrollCtl))) value = max;
  1811.  
  1812.         if (value != oldValue) {
  1813.             SetCtlValue(scrollCtl, value);
  1814.             teOffset = (*te)->viewRect.top - (*te)->destRect.top;
  1815.             if (value -= teOffset) TEScroll(0, -value, te);
  1816.         }
  1817.     }
  1818. }
  1819.  
  1820.  
  1821.  
  1822. /*****************************************************************************/
  1823.  
  1824.  
  1825.  
  1826. #pragma segment Controls
  1827. pascal void    HActionProc(ControlHandle scrollCtl, short part)
  1828. {
  1829.     short        lineHeight, delta, value, teOffset;
  1830.     short        oldValue, max;
  1831.     TEHandle    te;
  1832.     
  1833.     if (part) {                        /* If it was actually in the control. */
  1834.  
  1835.         te = gActiveTEHndl;
  1836.         lineHeight = (*te)->lineHeight;
  1837.  
  1838.         switch (part) {
  1839.             case inUpButton:
  1840.             case inDownButton:        /* One line. */
  1841.                 delta = lineHeight;
  1842.                 break;
  1843.             case inPageUp:            /* One page. */
  1844.             case inPageDown:
  1845.                 delta = (*te)->viewRect.right - (*te)->viewRect.left;
  1846.                 if (delta > lineHeight) delta -= lineHeight;
  1847.                 break;
  1848.         }
  1849.         if ( (part == inUpButton) || (part == inPageUp) )
  1850.             delta = -delta;        /* Reverse direction for an upper. */
  1851.  
  1852.         value = (oldValue = GetCtlValue(scrollCtl)) + delta;
  1853.         if (value < 0) value = 0;
  1854.         if (value > (max = GetCtlMax(scrollCtl))) value = max;
  1855.  
  1856.         if (value != oldValue) {
  1857.             SetCtlValue(scrollCtl, value);
  1858.             teOffset = (*te)->viewRect.left - (*te)->destRect.left;
  1859.             if (value -= teOffset) TEScroll(-value, 0, te);
  1860.         }
  1861.     }
  1862. }
  1863.  
  1864.  
  1865.  
  1866. /*****************************************************************************/
  1867.  
  1868.  
  1869.  
  1870. /* This function is called after an edit to make sure that there is no extra
  1871. ** white space at the bottom of the viewRect.  If there are blank lines at
  1872. ** the bottom of the viewRect, and there is text scrolled off the top of the
  1873. ** viewRect, then the TextEdit control is scrolled to fill this space, or as
  1874. ** much of it as possible.
  1875. */
  1876.  
  1877. #pragma segment Controls
  1878. void    AdjustTEBottom(TEHandle teHndl)
  1879. {
  1880.     Rect    destRect, viewRect;
  1881.     short    botDiff, topDiff;
  1882.  
  1883.     destRect = (*teHndl)->destRect;
  1884.     viewRect = (*teHndl)->viewRect;
  1885.     destRect.bottom = destRect.top + CTEDocHeight(teHndl);
  1886.  
  1887.     botDiff = viewRect.bottom - destRect.bottom;
  1888.     if (botDiff > 0) {
  1889.         topDiff = viewRect.top - destRect.top;
  1890.         if (botDiff > topDiff) botDiff = topDiff;
  1891.         if (botDiff) TEScroll(0, botDiff, teHndl);
  1892.     }
  1893.  
  1894. }
  1895.  
  1896.  
  1897.  
  1898. /*****************************************************************************/
  1899.  
  1900.  
  1901.  
  1902. /* Bring the scrollbar values up to date with the current document position
  1903. ** and length.
  1904. */
  1905.  
  1906. #pragma segment Controls
  1907. void    AdjustScrollValues(TEHandle teHndl)
  1908. {
  1909.     short            scrollNum;
  1910.     ControlHandle    scrollCtl;
  1911.  
  1912.     for (scrollNum = 0; scrollNum < 2; ++scrollNum) {
  1913.         scrollCtl = CTEScrollFromTE(teHndl, scrollNum);
  1914.         if (scrollCtl)
  1915.             AdjustOneScrollValue(teHndl, scrollCtl, scrollNum);
  1916.     }
  1917. }
  1918.  
  1919.  
  1920.  
  1921. /*****************************************************************************/
  1922.  
  1923.  
  1924.  
  1925. /* Bring one scrollbar value up to date with the current document position
  1926. ** and length.
  1927. */
  1928.  
  1929. #pragma segment Controls
  1930. void    AdjustOneScrollValue(TEHandle teHndl, ControlHandle ctl, Boolean vert)
  1931. {
  1932.     Boolean    front;
  1933.     short    textPix, viewPix;
  1934.     short    max, oldMax, value, oldValue;
  1935.  
  1936.     front = ((*ctl)->contrlOwner == FrontWindow());
  1937.  
  1938.     oldValue = GetCtlValue(ctl);
  1939.     oldMax   = GetCtlMax(ctl);
  1940.  
  1941.     if (vert) {
  1942.         textPix = CTEDocHeight(teHndl);
  1943.         viewPix = (*teHndl)->viewRect.bottom - (*teHndl)->viewRect.top;
  1944.     }
  1945.     else {
  1946.         textPix = (*teHndl)->destRect.right - (*teHndl)->destRect.left;
  1947.         viewPix = (*teHndl)->viewRect.right - (*teHndl)->viewRect.left;
  1948.     }
  1949.     max = textPix - viewPix;
  1950.  
  1951.     if (max < 0) max = 0;
  1952.     if (max != oldMax) {
  1953.         if (front) SetCtlMax(ctl, max);
  1954.         else       (*ctl)->contrlMax = max;
  1955.     }
  1956.  
  1957.     if (vert)
  1958.         value = (*teHndl)->viewRect.top  - (*teHndl)->destRect.top;
  1959.     else
  1960.         value = (*teHndl)->viewRect.left - (*teHndl)->destRect.left;
  1961.  
  1962.     if (value < 0)   value = 0;
  1963.     if (value > max) value = max;
  1964.     if (value != oldValue) {
  1965.         if (front) SetCtlValue(ctl, value);
  1966.         else       (*ctl)->contrlValue = value;
  1967.     }
  1968. }
  1969.  
  1970.  
  1971.  
  1972.